using JamesFan.FetchAreaCodes.Consumer;
using JamesFan.FetchAreaCodes.Parser;
using System.Diagnostics;
using Viyi.Util.Linq;

namespace JamesFan.FetchAreaCodes;

class DataWalker {
    public DataWalker(HttpClient httpClient, string url)
        : this(httpClient, new Uri(url)) { }

    public DataWalker(HttpClient httpClient, Uri uri) {
        HttpClient = httpClient;
        Uri = uri;
    }

    private readonly List<ICodeConsumer> consumers = new();
    private readonly Queue<CodeEntry> entryQueue = new();
    public int MaxLevel { get; set; } = 3;

    public HttpClient HttpClient { get; }
    public Uri Uri { get; }

    public async Task Walk() {
        Stopwatch stopwatch = Stopwatch.StartNew();

        await ParseTopPage();
        while (entryQueue.Count > 0) {
            var entry = entryQueue.Dequeue();
            await ParseContentPage(entry);
        }

        stopwatch.Stop();
        WriteLine($"[All done.] in {stopwatch.ElapsedMilliseconds / 1000.0} seconds.");
    }

    private async Task ParseContentPage(CodeEntry baseEntry) {
        if (baseEntry.Level >= MaxLevel) { return; }

        WriteLine($"[URL] {baseEntry.Uri}");
        var parser = new GeneralContentParser(baseEntry, await FetchContent(baseEntry.Uri!));
        await parser.ParseAsync();

        HandleResult(parser);
    }

    private async Task ParseTopPage() {
        string content = await FetchContent(Uri);
        var parser = new TopContentParser(
            new CodeEntry(null, "", "", Uri.ToString()),
            content
        );

        await parser.ParseAsync();
        HandleResult(parser);
    }

    private async Task<string> FetchContent(Uri uri) {
        using var resMessage = await TryFetch(uri);
        var stream = resMessage.Content.ReadAsStream();
        return await new StreamReader(stream).ReadToEndAsync();
    }

    private async Task<HttpResponseMessage> TryFetch(Uri uri, int times = 2) {
        var lastTryIndex = times - 1;
        for (int i = 0; i < times; i++) {
            try { return await HttpClient.GetAsync(uri); }
            catch (TaskCanceledException) when (i != lastTryIndex) { }
        }
        throw new InvalidOperationException("Should never run to here!");
    }

    public void AddConsumer(ICodeConsumer consumer) => consumers.Add(consumer);

    private void HandleResult(IParser parser) {
        consumers.ForEach(consumer => consumer.Consume(parser.Entries));

        if (parser.Entries[0].Level < MaxLevel) {
            parser.Entries
                .Where(it => it.Uri != null)
                .ForEach(entryQueue.Enqueue);
        }
    }
}
